/* 
 * File:   Application.cpp
 * Author: cjjonker
 * 
 * Created on 20 december 2012, 15:03
 */

#include "Application.h"
#include <iostream>
#include <GL/glut.h>
#include "Utilities.h"
#include <GL/glui.h>
#include "Visualizer.h"

using namespace std;

Simulation Application::simulation;			// the smoke simulation
Visualization Application::visualization;	// visualization of the simulation

//size of the graphics window, in pixels
int Application::winWidth;
int Application::winHeight;	

bool Application::setSeed;

GLUI* Application::glui;						// user interface
int Application::main_window;

GLUI_Button *pause_button;

vector<GLUI_Checkbox*> drawItCheckboxes;
vector<GLUI_Checkbox*> normalizeCheckboxes;
vector<GLUI_Listbox*> colorModeListboxes;
vector<GLUI_Listbox*> scaleModeListboxes;
vector<GLUI_Listbox*> colorMapDatasetListboxes;
vector<GLUI_Listbox*> datasetListboxes;
vector<GLUI_Listbox*> appearanceListboxes;

vector<GLUI_Spinner*> hueSpinners;
vector<GLUI_Spinner*> satSpinners;
vector<GLUI_Spinner*> valSpinners;
vector<GLUI_Spinner*> levelSpinners;
vector<GLUI_Spinner*> clampMaxSpinners;
vector<GLUI_Spinner*> clampMinSpinners;

vector<GLUI_Spinner*> vectorScaleSpinners;


void Application::update()
{
    glutSetWindow(main_window);
    simulation.do_one_simulation_step();
    glutPostRedisplay();
}

void Application::initialize(int *argc, char** argv)
{	
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(1000, 800);
    main_window = glutCreateWindow("Real-time smoke simulation and visualization");

    // blie bloe bla

    // pass static functions as callback to GLUT
    glutDisplayFunc(display);
    glutMotionFunc(drag);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouseHandler);

    // pass static functions as callback to GLUI
    GLUI_Master.set_glutReshapeFunc(reshape);  
    GLUI_Master.set_glutIdleFunc(update);

    simulation.init_simulation(Simulation::DIM);	//initialize the simulation data structures
    initUI();   
    
    visualization.setSimulation(&simulation);

    setWidgets();	
    glutMainLoop();							// enter main loop
    
}

//reshape: Handle window resizing (reshaping) events
void Application::reshape(int w, int h)
{
    glViewport(0.0f, 0.0f, (GLfloat)w, (GLfloat)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, (GLdouble)w, 0.0, (GLdouble)h, -1000.0, 1000.0);
    winWidth = w; 
    winHeight = h;
}

// output usage instructions
void Application::outputUsage()
{
    cout << "Fluid Flow Simulation and Visualization\n";
    cout << "=======================================\n";
    cout << "Click and drag the mouse to steer the flow!\n";
    cout << "T/t:   increase/decrease simulation timestep\n";
    cout << "S/s:   increase/decrease hedgehog scaling\n";
    cout << "c:     toggle direction coloring on/off\n";
    cout << "V/v:   increase decrease fluid viscosity\n";
    cout << "x:     toggle drawing matter on/off\n";
    cout << "y:     toggle drawing hedgehogs on/off\n";
    cout << "m:     toggle thru scalar coloring\n";
    cout << "a:     toggle the animation on/off\n";
    cout << "q:     quit\n\n";	
}

//keyboard: Handle key presses
void Application::keyboard(unsigned char key, int x, int y)
{
    switch (key)
    {
        case 't': { simulation.change_timestep(-0.001); } break;
        case 'T': { simulation.change_timestep(0.001); } break;
        /*
        case 'c': { visualization.toggle(Visualization::UseDirectionColoring); } break;
        case 'S': { visualization.scale_hedgehogs(1.2); } break;
        case 's': { visualization.scale_hedgehogs(0.8); } break;
         */
        case 'V': { simulation.scale_viscosity(5.0); } break;
        case 'v': { simulation.scale_viscosity(0.2); } break;
        case 'e': { setSeed = true; } break;
/*
        case 'x': 
        {
                visualization.toggle(Visualization::DrawSmoke);		
                if(visualization.is_enabled(Visualization::DrawSmoke))
                        visualization.enable(Visualization::DrawVectorField);				  		  
        } break;

        case 'y': 
        {				
            visualization.toggle(Visualization::DrawVectorField);		
            if(visualization.is_enabled(Visualization::DrawVectorField))
                    visualization.enable(Visualization::DrawSmoke);		
        } break;
*/
        case 'm': { } break;

        case 'a': { simulation.toggle_frozen(); } break;

        case 'q': { quit(); } break;

        default: {} break;
    }

    // sync live variables
    GLUI_Master.sync_live_all();	
}

// drag: When the user drags with the mouse, add a force that corresponds to the direction of the mouse
//       cursor movement. Also inject some new matter into the field at the mouse location.
void Application::drag(int mx, int my)
{
    int xi, yi, X, Y; 
    double  dx, dy, len;
    static int lmx=0,lmy=0;				//remembers last mouse location

    // Compute the array index that corresponds to the cursor location
    xi = (int)clamp((double)(Simulation::DIM + 1) * ((double)mx / (double)winWidth));
    yi = (int)clamp((double)(Simulation::DIM + 1) * ((double)(winHeight - my) / (double)winHeight));

    X = xi; Y = yi;

    if (X > (Simulation::DIM - 1))  
        X = Simulation::DIM - 1; 

    if (Y > (Simulation::DIM - 1))  
        Y = Simulation::DIM - 1;

    if (X < 0) X = 0; if (Y < 0) Y = 0;
    
    
    {
        // Add force at the cursor location
        my = winHeight - my;
        dx = mx - lmx; dy = my - lmy;
        len = sqrt(dx * dx + dy * dy);
        if (len != 0.0) {  dx *= 0.1 / len; dy *= 0.1 / len; }
        simulation.insert_force(X, Y, dx, dy);	
        lmx = mx; 
        lmy = my;
    }
}

void Application::mouseHandler(int button, int state, int mx, int my)
{    // Compute the array index that corresponds to the cursor location
    
    
    if (setSeed && button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        int xi = double(Simulation::DIM + 1) * double(mx) / double(winWidth);
        int yi = double(Simulation::DIM + 1) * double(winHeight - my) / double(winHeight);
        visualization.setSeed(xi, yi);
        setSeed = false;
    }
    
}

void Application::buttonHandler(int id)
{
    switch(id)
    {
        case QuitButton : { quit(); } break;
        case PauseButton: { pause(); } break;
        case SeedButton: {setSeed = true; } break;
        default: {} break;
    }
}

void Application::drawItCheckBoxHandler(int id)
{
    visualization.getVisualizers().at(id)->setActive(drawItCheckboxes.at(id)->get_int_val());
}

void Application::colorModeListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentMapColormode(colorModeListboxes.at(id)->get_int_val());
}

void Application::colorMapDatasetListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentMapDataset(colorMapDatasetListboxes.at(id)->get_int_val());
}

void Application::scaleModeListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentMapScalemode(scaleModeListboxes.at(id)->get_int_val());
}

void Application::datasetListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentVectorDataset(datasetListboxes.at(id)->get_int_val());
}

void Application::valSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapVal(valSpinners.at(id)->get_float_val());
}

void Application::hueSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapHue(hueSpinners.at(id)->get_float_val());
}

void Application::satSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapSat(satSpinners.at(id)->get_float_val());
}

void Application::levelSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setColormapLevels(levelSpinners.at(id)->get_int_val());
}

void Application::appearanceListboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setCurrentAppearance(appearanceListboxes.at(id)->get_int_val());
}

void Application::normalizeCheckboxHandler(int id)
{
    visualization.getVisualizers().at(id)->setNormalize(normalizeCheckboxes.at(id)->get_int_val());
}

void Application::clampMaxSpinnerHandler(int id)
{
    clampMaxSpinners.at(id)->set_float_limits(clampMinSpinners.at(id)->get_float_val()+clampRange, clampMax, clampFunction);
    clampMinSpinners.at(id)->set_float_limits(clampMin, clampMaxSpinners.at(id)->get_float_val()-clampRange, clampFunction);
    visualization.getVisualizers().at(id)->setCurrentMapClampMax(clampMaxSpinners.at(id)->get_float_val());
}

void Application::clampMinSpinnerHandler(int id)
{
    clampMaxSpinners.at(id)->set_float_limits(clampMinSpinners.at(id)->get_float_val()+clampRange, clampMax, clampFunction);
    clampMinSpinners.at(id)->set_float_limits(clampMin, clampMaxSpinners.at(id)->get_float_val()-clampRange, clampFunction);
    visualization.getVisualizers().at(id)->setCurrentMapClampMin(clampMinSpinners.at(id)->get_float_val());
}

void Application::vectorScaleSpinnerHandler(int id)
{
    visualization.getVisualizers().at(id)->setVectorScale(vectorScaleSpinners.at(id)->get_float_val());
}


void Application::initUI()
{
    const int width = 400;
    setSeed = false;
    glui = GLUI_Master.create_glui("Controls");
    glui->set_main_gfx_window(main_window);   
       
    for (int i = 0;
            i != visualization.getVisualizers().size(); ++i)
    {
        Visualizer *vis = visualization.getVisualizers().at(i);
        
        const char *visualizername = vis->getName().c_str();
        
        GLUI_Rollout *currentRollout =glui->add_rollout(visualizername, true);
        
        drawItCheckboxes.push_back(glui->add_checkbox_to_panel(currentRollout,"Draw it",NULL, i,drawItCheckBoxHandler));
        
        
        // this part is only for vector visualizers
        if (vis->usesVectorDataset())
        {
            datasetListboxes.push_back(
                makeListboxFromVector(vis->getVectorDatasetNames(), "Visualized Vectorfield: ", i, currentRollout, datasetListboxHandler)
                );
                
            GLUI_Spinner* currentScaleSpinner = glui->add_spinner_to_panel(currentRollout,"Vector scale",GLUI_SPINNER_FLOAT,NULL,i,vectorScaleSpinnerHandler);
            
            currentScaleSpinner->set_float_limits(1,10,GLUI_LIMIT_CLAMP);
            currentScaleSpinner->set_float_val(1.0);       
            vectorScaleSpinners.push_back(currentScaleSpinner);
            
            normalizeCheckboxes.push_back(
                glui->add_checkbox_to_panel(currentRollout,"Normalize",NULL,i,normalizeCheckboxHandler)
                );
            
        }
        else
        {
            vectorScaleSpinners.push_back(NULL);
            datasetListboxes.push_back(NULL);
            normalizeCheckboxes.push_back(NULL);
        }
        
        
        
        if (vis->hasMultipleAppearances())
        {
            appearanceListboxes.push_back(
            makeListboxFromVector(vis->getAppearancesNames(), "Appearace: ", i, currentRollout, appearanceListboxHandler)
                    );
        }
        else
        {
            appearanceListboxes.push_back(NULL);
        }
        
        GLUI_Panel *colorMapPanel = new GLUI_Panel(currentRollout, "Colormap");
        
        colorModeListboxes.push_back(
                makeListboxFromVector(vis->getColorModeNames(), "Colormode: ", i, colorMapPanel, colorModeListboxHandler)
                );
        
        colorMapDatasetListboxes.push_back(
                makeListboxFromVector(vis->getColormapDatasets(), "Colormap Dataset: ", i, colorMapPanel, colorMapDatasetListboxHandler)
                );
        
        
        
        scaleModeListboxes.push_back(
                makeListboxFromVector(vis->getScaleModeNames(), "Colormap Scale Mode: ", i, colorMapPanel, scaleModeListboxHandler)
                );
        
        GLUI_Spinner* currentSpinner;
        // clamp maximum spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Clamp maximum: ", GLUI_SPINNER_FLOAT, NULL, i, clampMaxSpinnerHandler);
        currentSpinner->set_float_limits(clampMin, clampMax, clampFunction);
        currentSpinner->set_float_val(1.0);
        clampMaxSpinners.push_back(currentSpinner);
        
        //clamp minimum spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Clamp minimum: ", GLUI_SPINNER_FLOAT, NULL, i, clampMinSpinnerHandler);
        currentSpinner->set_float_limits(clampMin, clampMax, clampFunction);
        currentSpinner->set_float_val(0.0);
        clampMinSpinners.push_back(currentSpinner);
        
        //hue spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Colormap hue: ", GLUI_SPINNER_FLOAT, NULL, i, hueSpinnerHandler);
        currentSpinner->set_float_limits(hsvmin, hsvmax, clampFunction);
        currentSpinner->set_float_val(0.0);
        hueSpinners.push_back(currentSpinner);
       
        //saturation spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Colormap saturation: ", GLUI_SPINNER_FLOAT, NULL, i, satSpinnerHandler);
        currentSpinner->set_float_limits(hsvmin, hsvmax, clampFunction);
        currentSpinner->set_float_val(1.0);        
        satSpinners.push_back(currentSpinner);
        
        //value spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Colormap value: ", GLUI_SPINNER_FLOAT, NULL, i, valSpinnerHandler);
        currentSpinner->set_float_limits(hsvmin, hsvmax, clampFunction);
        currentSpinner->set_float_val(1.0);
        valSpinners.push_back(currentSpinner);
        
        //level spinner
        currentSpinner = glui->add_spinner_to_panel(colorMapPanel, "Number of colors: ", GLUI_SPINNER_INT, NULL, i, levelSpinnerHandler);
        currentSpinner->set_int_limits(levelmin, levelmax, clampFunction);
        currentSpinner->set_int_val(100);
        levelSpinners.push_back(currentSpinner);
        
        currentRollout->close();
        
    }
          
    new GLUI_Button(glui, "Set seed", SeedButton, buttonHandler);
    pause_button = new GLUI_Button(glui, "Freeze", PauseButton, buttonHandler);
    new GLUI_Button(glui, "Quit", QuitButton, buttonHandler);
}

GLUI_Listbox* Application::makeListboxFromVector(std::vector<std::string> vec, std::string name, int id, GLUI_Panel *panel, GLUI_Update_CB callBack)
{
    GLUI_Listbox* listbox = glui->add_listbox_to_panel(panel,name.c_str(),NULL,id,callBack);
    for (int i = 0; i < vec.size(); ++i)
    {
        const char *modename = vec.at(i).c_str();
        listbox->add_item(i, modename);
    }
    return listbox;
}

//display: Handle window redrawing events. Simply delegates to visualize().
void Application::display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glEnable(GL_NORMALIZE);
    glEnable(GL_COLOR_MATERIAL);
    
    GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat mat_shininess[] = { 50.0 };
    GLfloat light_position[] = { 400, 400, 50.0, 0.0 };
    glShadeModel(GL_SMOOTH);

    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);    

    GLfloat ambientColor[] = {0.8, 0.8, 0.8, 1.0}; //Color(0.2, 0.2, 0.2)
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor);
    
    //glLoadIdentity();
    //gluLookAt(0.0, -200.0, 200.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);
    
    visualization.visualize(winWidth, winHeight);    
    glFlush();
    glutSwapBuffers();
}

void Application::setWidgets()
{
}

void Application::quit()
{
    exit(0);
}

void Application::pause()
{
    simulation.toggle_frozen();
    if (simulation.frozen) {
        pause_button->set_name("Unfreeze");
    }
    else
    {
        pause_button->set_name("Freeze");
    }
}

